package fr.hotel.services;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import fr.hotel.bo.Chambre;
import fr.hotel.bo.Client;
import fr.hotel.bo.Hotel;
import fr.hotel.bo.affinite.Affinite;
import fr.hotel.bo.affinite.AffiniteVide;
import fr.hotel.bo.affinite.Bruit;
import fr.hotel.bo.affinite.Chaleur;
import fr.hotel.bo.affinite.Humidite;
import fr.hotel.bo.affinite.Nourriture;
import fr.hotel.bo.affinite.Odeur;
import fr.hotel.bo.affinite.Voisinnage;
import fr.hotel.constantes.IConstantes;
import fr.hotel.xml.ParserXML;

public class ServiceHotel {

	private Document planXML;
	private ServiceClient serviceClient;

	public ServiceHotel(ServiceClient serviceClient) {
		this.serviceClient = serviceClient;
	}

	public Hotel construction(String plan) throws ParserConfigurationException,
			SAXException, IOException {
		Hotel hotel = new Hotel();
		planXML = ParserXML.parserDocument(plan);
		hotel.setNbChambre(getNbChambre());
		hotel.setChambres(getInfoChambres());
		return hotel;
	}

	private List<Chambre> getInfoChambres() {
		List<Chambre> chambres = new ArrayList<Chambre>();
		NodeList chambresXML = planXML
				.getElementsByTagName(IConstantes.PLAN_CHAMBRE);
		int size = chambresXML.getLength();
		for (int i = 0; i < size; i++) {
			Node chambre = chambresXML.item(i);
			NodeList attributChambre = chambre.getChildNodes();
			Chambre chambreTmp = new Chambre();
			for (int j = 0; j < attributChambre.getLength(); j++) {
				Node noeudChambre = attributChambre.item(j);
				if (IConstantes.PLAN_X.equals(noeudChambre.getNodeName())) {
					chambreTmp.setX(Integer.valueOf(noeudChambre
							.getTextContent()));
				} else if (IConstantes.PLAN_Y
						.equals(noeudChambre.getNodeName())) {
					chambreTmp.setY(Integer.valueOf(noeudChambre
							.getTextContent()));
				} else if (IConstantes.PLAN_BONUS.equals(noeudChambre
						.getNodeName())) {
					NodeList attributBonus = noeudChambre.getChildNodes();
					for (int k = 0; k < attributBonus.getLength(); k++) {
						Node type = attributBonus.item(i);
						if (type != null && type.hasChildNodes()) {
							if (type.getFirstChild().getTextContent() != null
									&& !"".equals(type.getFirstChild()
											.getTextContent().trim())) {
								chambreTmp.getBonus().add(
										Integer.valueOf(type.getFirstChild()
												.getTextContent().trim()));
							}
						}
					}
				}
			}
			chambres.add(chambreTmp);
		}
		return chambres;
	}

	private Integer getNbChambre() {
		Integer nb = new Integer(0);
		NodeList chambres = planXML
				.getElementsByTagName(IConstantes.PLAN_CHAMBRE);
		nb = chambres.getLength();
		return nb;
	}

	public Hotel alouerChambres(Hotel hotel, List<Client> clients) {
		Map<String, List<Client>> clientsAffinite = new HashMap<String, List<Client>>();
		Map<String, List<Client>> clientsNonAffinite = new HashMap<String, List<Client>>();
		Map<String, List<Client>> clientsDonnee = new HashMap<String, List<Client>>();
		Map<String, List<Client>> tmp = new HashMap<String, List<Client>>();
		Affinite affinite = null;
		affinite = new Bruit();
		tmp = serviceClient.getClientsParAffinite(clients, affinite);
		clientsAffinite.put(affinite.getIdentifiant(), tmp.get("aime"));
		clientsNonAffinite.put(affinite.getIdentifiant(), tmp.get("nonaime"));
		clientsDonnee.put(affinite.getIdentifiant(), tmp.get("donnee"));
		affinite = new Chaleur();
		tmp = serviceClient.getClientsParAffinite(clients, affinite);
		clientsAffinite.put(affinite.getIdentifiant(), tmp.get("aime"));
		clientsNonAffinite.put(affinite.getIdentifiant(), tmp.get("nonaime"));
		clientsDonnee.put(affinite.getIdentifiant(), tmp.get("donnee"));
		affinite = new Humidite();
		tmp = serviceClient.getClientsParAffinite(clients, affinite);
		clientsAffinite.put(affinite.getIdentifiant(), tmp.get("aime"));
		clientsNonAffinite.put(affinite.getIdentifiant(), tmp.get("nonaime"));
		clientsDonnee.put(affinite.getIdentifiant(), tmp.get("donnee"));
		affinite = new Nourriture();
		tmp = serviceClient.getClientsParAffinite(clients, affinite);
		clientsAffinite.put(affinite.getIdentifiant(), tmp.get("aime"));
		clientsNonAffinite.put(affinite.getIdentifiant(), tmp.get("nonaime"));
		clientsDonnee.put(affinite.getIdentifiant(), tmp.get("donnee"));
		affinite = new Odeur();
		tmp = serviceClient.getClientsParAffinite(clients, affinite);
		clientsAffinite.put(affinite.getIdentifiant(), tmp.get("aime"));
		clientsNonAffinite.put(affinite.getIdentifiant(), tmp.get("nonaime"));
		clientsDonnee.put(affinite.getIdentifiant(), tmp.get("donnee"));
		affinite = new Voisinnage();
		tmp = serviceClient.getClientsParAffinite(clients, affinite);
		clientsAffinite.put(affinite.getIdentifiant(), tmp.get("aime"));
		clientsNonAffinite.put(affinite.getIdentifiant(), tmp.get("nonaime"));
		clientsDonnee.put(affinite.getIdentifiant(), tmp.get("donnee"));

		Map<Client, List<Client>> graphClientOK = new HashMap<Client, List<Client>>();
		Map<Client, List<Client>> graphClientKO = new HashMap<Client, List<Client>>();
		for (Client client : clients) {
			System.out.println(client.getId() + " => "
					+ graphClientOK.containsKey(client));
			graphClientOK.put(client, new ArrayList<Client>());
			graphClientKO.put(client, new ArrayList<Client>());
		}
		for (Client client : clients) {
			Affinite recherche1 = (client.getAffinite1().getAffinite()
					.equals("1")) ? client.getAffinite1() : new AffiniteVide();
			Affinite recherche2 = (client.getAffinite2().getAffinite()
					.equals("1")) ? client.getAffinite2() : new AffiniteVide();
			Affinite refuse1 = (client.getAffinite1().getAffinite()
					.equals("-1")) ? client.getAffinite1() : new AffiniteVide();
			Affinite refuse2 = (client.getAffinite2().getAffinite()
					.equals("-1")) ? client.getAffinite2() : new AffiniteVide();
			// Personne ne donne de l'affinite 1
			if (clientsDonnee.get(recherche1.getIdentifiant()) == null
					|| clientsDonnee.get(recherche1.getIdentifiant()).isEmpty()) {
				// Personne ne donne de l'affinite 2 ==> On passe au client
				// suivant
				if (clientsDonnee.get(recherche2.getIdentifiant()) == null
						|| clientsDonnee.get(recherche2.getIdentifiant())
								.isEmpty()) {
					// on ne fait rien
				}
				// Des clients donne l'affinite 2
				else {
					for (Client clientDonnateur : clientsDonnee.get(recherche2
							.getIdentifiant())) {
						if (clientDonnateur.getId() != client.getId()) {
							graphClientOK.get(client).add(clientDonnateur);
						}
					}
				}
				// Des clients donne l'affinite 1
			} else {
				for (Client clientDonnateur : clientsDonnee.get(recherche1
						.getIdentifiant())) {
					if (clientDonnateur.getId() != client.getId()) {
						graphClientOK.get(client).add(clientDonnateur);
					}
				}
			}
			// Personne ne donne de l'affinite 1
			if (clientsDonnee.get(refuse1.getIdentifiant()) == null
					|| clientsDonnee.get(refuse1.getIdentifiant()).isEmpty()) {
				// Personne ne donne de l'affinite 2 ==> On passe au client
				// suivant
				if (clientsDonnee.get(refuse2.getIdentifiant()) == null
						|| clientsDonnee.get(refuse2.getIdentifiant())
								.isEmpty()) {
					// on ne fait rien
				}
				// Des clients donne l'affinite 2
				else {
					for (Client clientDonnateur : clientsDonnee.get(refuse2
							.getIdentifiant())) {
						if (clientDonnateur.getId() != client.getId()) {
							graphClientKO.get(client).add(clientDonnateur);
						}
					}
				}
				// Des clients donne l'affinite 1
			} else {
				for (Client clientDonnateur : clientsDonnee.get(refuse1
						.getIdentifiant())) {
					if (clientDonnateur.getId() != client.getId()) {
						graphClientKO.get(client).add(clientDonnateur);
					}
				}
			}
		}

		for (Client clientAPlacer : clients) {
			getChambrePourClient(clientAPlacer, graphClientOK, graphClientKO,
					hotel);
		}

		return hotel;
	}

	private void getChambrePourClient(Client clientAPlacer,
			Map<Client, List<Client>> graphClientOK,
			Map<Client, List<Client>> graphClientKO, Hotel hotel) {
		List<Chambre> chambresDispo = new ArrayList<Chambre>();
		List<Chambre> chambresAvecBonus = new ArrayList<Chambre>();
		List<Chambre> chambresAvecBonClient = new ArrayList<Chambre>();
		List<Chambre> chambresAvecClient = new ArrayList<Chambre>();
		// On recupere les chambres qui n'ont pas de client.
		for (Chambre chambre : hotel.getChambres()) {
			if (chambre.getClient() == null) {
				chambresDispo.add(chambre);
			}
		}

		chambresAvecClient.addAll(hotel.getChambres());
		chambresAvecClient.removeAll(chambresDispo);
		
		// On cherche les chambres avec le bon bonus
		for (Chambre chambre : chambresDispo) {
			for (Integer bonus : chambre.getBonus()) {
				if ((bonus.equals(Integer.valueOf(clientAPlacer.getAffinite1()
						.getIdentifiant())) && clientAPlacer.getAffinite1()
						.getAffinite().equals("1"))
						|| (bonus.equals(Integer.valueOf(clientAPlacer
								.getAffinite2().getIdentifiant())) && clientAPlacer
								.getAffinite2().getAffinite().equals("1"))) {
					chambresAvecBonus.add(chambre);
				}
			}
		}

		chambresDispo.removeAll(chambresAvecBonus);
		boolean chambreTrouve = false;
		for (Chambre chambre : chambresAvecBonus) {
			if (chambreOK(chambre, clientAPlacer, hotel.getChambres())) {
				int idx = hotel.getChambres().indexOf(chambre);
				hotel.getChambres().get(idx).setClient(clientAPlacer);
				chambreTrouve = true;
				break;
			}
		}

		if (!chambreTrouve) {
			chambreTrouve = false;
			
			for (Chambre chambre : chambresAvecClient) {
				String voisinDonnee = chambre.getClient().getDonne().getIdentifiant();
				if ((clientAPlacer.getAffinite1().getIdentifiant().equals(voisinDonnee)
						&& clientAPlacer.getAffinite1().getAffinite().equals("1")) || (clientAPlacer.getAffinite2().getIdentifiant().equals(voisinDonnee)
								&& clientAPlacer.getAffinite2().getAffinite().equals("1"))) {
					if (chambreOK(chambre, clientAPlacer, hotel.getChambres())) {
						int idx = hotel.getChambres().indexOf(chambre);
						hotel.getChambres().get(idx).setClient(clientAPlacer);
						chambreTrouve = true;
						break;
					}
				}
			}
			if (!chambreTrouve) {
				for (Chambre chambre : chambresDispo) {
					if (chambreOK(chambre, clientAPlacer, hotel.getChambres())) {
						int idx = hotel.getChambres().indexOf(chambre);
						hotel.getChambres().get(idx).setClient(clientAPlacer);
						chambreTrouve = true;
						break;
					}
				}
			}
		}
	}

	private boolean chambreOK(Chambre chambre, Client clientAPlacer,
			List<Chambre> chambres) {
		List<Chambre> chambresVoisinnes = new ArrayList<Chambre>();
		chambresVoisinnes = getChambresVoisinnes(chambres, chambre);

		int cc = verifierCompatibilite(clientAPlacer, chambresVoisinnes);

		return (cc == 1 || cc == 0) ? true : false;
	}

	private int verifierCompatibilite(Client clientAPlacer,
			List<Chambre> chambresVoisinnes) {
		String donneeParClient = clientAPlacer.getDonne().getIdentifiant();
		for (Chambre chambre : chambresVoisinnes) {
			Client voisin = chambre.getClient();
			if (voisin != null) {
				if ((voisin.getAffinite1().getIdentifiant()
						.equals(donneeParClient) && voisin.getAffinite1()
						.getAffinite().equals("-1"))
						|| (voisin.getAffinite2().getIdentifiant()
								.equals(donneeParClient) && voisin
								.getAffinite2().getAffinite().equals("-1"))) {
					return -1;
				} else {
					String voisinDonnee = voisin.getDonne().getIdentifiant();
					if ((clientAPlacer.getAffinite1().getAffinite().equals("1") && clientAPlacer
							.getAffinite1().getIdentifiant()
							.equals(voisinDonnee))
							|| (clientAPlacer.getAffinite1().getAffinite()
									.equals("1") && clientAPlacer
									.getAffinite1().getIdentifiant()
									.equals(voisinDonnee))) {
						return 1;
					}
				}
			}
		}
		return 0;
	}

	private List<Chambre> getChambresVoisinnes(List<Chambre> chambres,
			Chambre chambre) {
		List<Chambre> chambresVoisinnes = new ArrayList<Chambre>();

		// On cherche les 4 chambres voisinnes !
		Integer x = chambre.getX();
		Integer y = chambre.getY();
		Chambre tmp = getChambreByXY(chambres, x + 1, y);
		if (tmp != null)
			chambresVoisinnes.add(tmp);
		tmp = getChambreByXY(chambres, x - 1, y);
		if (tmp != null)
			chambresVoisinnes.add(tmp);
		tmp = getChambreByXY(chambres, x, y + 1);
		if (tmp != null)
			chambresVoisinnes.add(tmp);
		tmp = getChambreByXY(chambres, x, y - 1);
		if (tmp != null)
			chambresVoisinnes.add(tmp);

		return chambresVoisinnes;
	}

	private Chambre getChambreByXY(List<Chambre> chambres, Integer x, Integer y) {
		for (Chambre chambre : chambres) {
			if (chambre.getX().equals(x) && chambre.getY().equals(y)) {
				return chambre;
			}
		}
		return null;
	}

}
